//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.ManagementStudioControls.RegistryManagerModule
{
    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using ManagementStudioServices.Interfaces;
    using Microsoft.Research.ScientificWorkflow.ManagementStudioControls.Common;
    using Microsoft.Research.ScientificWorkflow.ManagementStudioModels;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.WpfComposite;
    using Microsoft.Practices.Unity;
    using Microsoft.Win32;
    using SR = Microsoft.Research.DataLayer;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.Import;

    public class ActivityCenterPanePresenter : IRegistryManagerCenterPanePresenter, INotifyPropertyChanged
    {
        #region Private declarations

        /// <summary>
        /// Space character
        /// </summary>
        private const string SPACE = " ";

        private IUnityContainer container;

        private IRegistryManagerService registryManagerService;

        private ActivityViewModel editableActivityViewModel;

        private ActivityViewModel selectedViewModel;

        private CategoryViewModel categoryPickerSelectedModel;

        private CategoryPicker categoryPicker;

        private bool saveInProgress;

        #endregion Private Declarations

        #region Constructor

        /// <summary>
        /// The presenter for the activity manager.
        /// </summary>
        /// <param name="container">
        /// The unity container that enables this object to access all relevant items.
        /// </param>
        /// <param name="contentView">
        /// The main View.
        /// </param>
        /// <param name="service">
        /// The registry manager service giving access to the registry.
        /// </param>
        public ActivityCenterPanePresenter(IUnityContainer container, ActivityCatalogCenterPane contentView, IRegistryManagerService service)
        {
            this.container = container;
            this.ContentView = contentView;
            this.registryManagerService = service;
            contentView.DataContext = this;

            this.DeleteActivityCommand = new DelegateCommand<object>(p => this.DeleteActivityExecute());
            this.ImportActivityCommand = new DelegateCommand<object>(p => this.ImportActivityExecute());
            this.SaveActivityChangesCommand = new DelegateCommand<object[]>(this.SaveActivityChangesExecute);
            this.CancelActivityChangesCommand = new DelegateCommand<object>(p => this.CancelActivityChangesExecute());

            this.BrowseImageCommand = new DelegateCommand<object>(p => this.BrowseImageExecute());
            this.RevertToDefIconCommand = new DelegateCommand<object>(p => this.RevertToDefIconExecute());

            this.BrowseCategoryCommand = new DelegateCommand<object>(p => this.BrowseCategoryExecute());
        }

        #endregion Constructor

        #region Properties

        /// <summary>
        /// Gets or sets the editable category model.
        /// </summary>
        /// <value>The editable category model.</value>
        public ActivityViewModel EditableActivityViewModel
        {
            get
            {
                return this.editableActivityViewModel;
            }
            set
            {
                this.editableActivityViewModel = value;
                this.OnNotifyPropertyChanged("EditableActivityViewModel");
            }
        }

        /// <summary>
        /// Import activity command.
        /// </summary>
        public DelegateCommand<object> ImportActivityCommand { get; private set; }

        /// <summary>
        /// Gets or sets the delete category command.
        /// </summary>
        /// <value>The delete category command.</value>
        public DelegateCommand<object> DeleteActivityCommand { get; private set; }

        /// <summary>
        /// Gets or sets the browse category command.
        /// </summary>
        /// <value>The browse category command.</value>
        public DelegateCommand<object> BrowseCategoryCommand { get; private set; }

        /// <summary>
        /// Gets or sets the save category changes command.
        /// </summary>
        /// <value>The save category changes command.</value>
        public DelegateCommand<object[]> SaveActivityChangesCommand { get; private set; }

        /// <summary>
        /// Gets or sets the cancel category changes command.
        /// </summary>
        /// <value>The cancel category changes command.</value>
        public DelegateCommand<object> CancelActivityChangesCommand { get; private set; }

        /// <summary>
        /// Gets or sets the browse image command.
        /// </summary>
        /// <value>The browse image command.</value>
        public DelegateCommand<object> BrowseImageCommand { get; private set; }

        /// <summary>
        /// Gets or sets the revert to def icon command.
        /// </summary>
        /// <value>The revert to def icon command.</value>
        public DelegateCommand<object> RevertToDefIconCommand { get; private set; }

        #endregion Properties

        #region IRegistryManagerCenterPanePresenter Members

        /// <summary>
        /// Gets or sets the content view.
        /// </summary>
        public UIElement ContentView
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the refresh command.
        /// </summary>
        /// <value>The refresh command.</value>
        public DelegateCommand<BaseRegMgrModel> RefreshCommand { get; set; }

        /// <summary>
        /// Gets or sets the update command.
        /// </summary>
        /// <value>The delete command.</value>
        public DelegateCommand<BaseRegMgrModel> UpdateCommand { get; set; }

        /// <summary>
        /// Gets or sets the delete command.
        /// </summary>
        /// <value>The delete command.</value>
        public DelegateCommand<BaseRegMgrModel> DeleteCommand { get; set; }

        /// <summary>
        /// Gets or sets the select root command.
        /// </summary>
        /// <value>The select root command.</value>
        public DelegateCommand<object> SelectRootCommand { get; set; }

        /// <summary>
        /// Gets or sets the selected category view model.
        /// </summary>
        /// <value>The selected category view model.</value>
        public BaseRegMgrModel SelectedModel
        {
            get
            {
                return this.selectedViewModel;
            }
            set
            {
                this.selectedViewModel = value as ActivityViewModel;
                if (this.selectedViewModel != null)
                {
                    Guid currentId = this.selectedViewModel.Id;
                    if (null != this.EditableActivityViewModel && !this.EditableActivityViewModel.Id.Equals(currentId))
                    {
                        this.container.Resolve<IStatusMessageControl>().SetText(string.Empty);
                    }
                    try
                    {
                        if (null == this.selectedViewModel.RelatedWorkflows)
                        {
                            this.selectedViewModel.RelatedWorkflows = new ObservableCollection<WorkflowViewModel>(this.registryManagerService.FetchRelatedWorkflows(this.selectedViewModel));
                        }
                    }
                    catch (TridentCustomException ex)
                    {
                        TridentMessageBox.ShowTridentErrorMessageBox(ex.CustomMessage.ToString());
                    }
                }

                this.EditableActivityViewModel = ActivityViewModel.CreateClone(this.selectedViewModel);
                this.OnNotifyPropertyChanged("SelectedModel");
            }
        }

        /// <summary>
        /// Saves the changes.
        /// </summary>
        /// <returns>True if save is success.</returns>
        public SaveStateHandler SaveChanges()
        {
            return this.EnforceSaveForChanges();
        }

        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Called by a property when it wants to notify that its value has changed.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        private void OnNotifyPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

        #region Methods

        private void SaveActivityChangesExecute(object[] parameters)
        {
            bool saveSucceeded = true;
            this.saveInProgress = true;
            StringBuilder errorMsg = new StringBuilder(ManagementStudioResourceManager.GetString("SaveErrorRegistryMgr"));
            try
            {
                saveSucceeded = this.PreSaveValidation();
                if (saveSucceeded)
                {
                    SR.Activity savedActivity = this.registryManagerService.SaveActivity(this.EditableActivityViewModel);
                    saveSucceeded = savedActivity != null;
                    if (saveSucceeded)
                    {
                        this.EditableActivityViewModel.Update(savedActivity);
                        this.EditableActivityViewModel.IsSelected = true;
                    }
                }
                else
                {
                    errorMsg.Append(" ");
                    errorMsg.Append(ManagementStudioResourceManager.GetString("InvalidPreSaveCheck"));
                }
            }
            catch (TridentCustomException ex)
            {
                errorMsg.Append(ex.Message);
                saveSucceeded = false;
            }

            if (saveSucceeded)
            {
                if (this.UpdateCommand != null)
                {
                    this.RefreshCommand.Execute(this.EditableActivityViewModel);
                    this.EditableActivityViewModel = ActivityViewModel.CreateClone(this.SelectedModel as ActivityViewModel);
                }

                this.container.Resolve<IStatusMessageControl>().SetText(ManagementStudioResourceManager.GetString("SaveSuccessActivityMgr"));
            }
            else
            {
                TridentMessageBox.ShowTridentErrorMessageBox(errorMsg.ToString());
            }

            if (parameters != null && parameters.Length > 1)
            {
                SaveStateHandler saveSuccess = parameters[1] as SaveStateHandler;
                if (saveSuccess != null)
                {
                    saveSuccess.SaveOptionSelected = saveSucceeded ? SaveStatus.Valid : SaveStatus.Invalid;
                    if (!saveSucceeded && !this.SelectedModel.IsSelected)
                    {
                        // If the save has failed, reset the previous model as selected. The current selection will be what the 
                        // user has selected on the left pane, which in turn launched this flow.
                        ActivityViewModel backUpModel = ActivityViewModel.CreateClone(this.EditableActivityViewModel);
                        this.Rollback();
                        this.SelectedModel.IsSelected = true;
                        this.EditableActivityViewModel = ActivityViewModel.CreateClone(backUpModel);
                    }
                }
            }

            this.saveInProgress = false;
        }

        private bool PreSaveValidation()
        {
            bool readyForSave = true;
            if (this.EditableActivityViewModel != null)
            {
                if (string.IsNullOrEmpty(this.EditableActivityViewModel.Label) ||
                    string.IsNullOrEmpty(this.EditableActivityViewModel.Label.Trim()))
                {
                    readyForSave = false;
                }

                if (readyForSave)
                {
                    readyForSave = !this.EditableActivityViewModel.InputParameters.Any(item => item.ContainsInvalidValue);
                }
            }
            else
            {
                readyForSave = false;
            }

            return readyForSave;
        }

        private void CancelActivityChangesExecute()
        {
            this.Rollback();
            if (this.SelectRootCommand != null)
            {
                this.SelectRootCommand.Execute(null);
            }
        }

        private void Rollback()
        {
            if (this.SelectedModel != null)
            {
                this.EditableActivityViewModel = ActivityViewModel.CreateClone(this.SelectedModel as ActivityViewModel);
            }
        }

        private void RevertToDefIconExecute()
        {
            this.EditableActivityViewModel.Icon = null;
            this.EditableActivityViewModel.IconPath = string.Empty;
        }

        private void BrowseImageExecute()
        {
            string fileSelected = string.Empty;

            OpenFileDialog openDialog = new OpenFileDialog();

            // Filters the XOML file in the current Directory.
            openDialog.Filter = "Image Files (*.png, *.jpg, *.jpeg, *.ico, *.bmp)|*.png; *.jpg; *.jpeg; *.ico; *.bmp";

            // SUer should select only one file to open.
            openDialog.Multiselect = false;

            // Checks and promts the user if the file is not present in the directory.
            openDialog.CheckFileExists = true;

            // Check if File Exists in directory Specified.
            openDialog.CheckPathExists = true;

            // Restores the previous Directory the user had selected to Open/Save the file.
            openDialog.RestoreDirectory = true;

            if ((bool)openDialog.ShowDialog())
            {
                // Get the selected file name.
                fileSelected = openDialog.FileName;
            }

            if (!string.IsNullOrEmpty(fileSelected) && File.Exists(fileSelected))
            {
                string selectedExtn = System.IO.Path.GetExtension(fileSelected);
                bool validFile = (selectedExtn.Equals(".png", StringComparison.OrdinalIgnoreCase)
                    || selectedExtn.Equals(".ico", StringComparison.OrdinalIgnoreCase)
                    || selectedExtn.Equals(".jpg", StringComparison.OrdinalIgnoreCase)
                    || selectedExtn.Equals(".jpeg", StringComparison.OrdinalIgnoreCase)
                    || selectedExtn.Equals(".bmp", StringComparison.OrdinalIgnoreCase));
                if (validFile)
                {
                    this.EditableActivityViewModel.IconPath = fileSelected;
                }
                else
                {
                    TridentMessageBox.ShowTridentErrorMessageBox(ManagementStudioResourceManager.GetString("InvalidImageFileSelection"));
                    this.BrowseImageCommand.Execute(null);
                }
            }
        }

        /// <summary>
        /// Deletes the activity 
        /// </summary>
        private void DeleteActivityExecute()
        {
            try
            {
                MessageBoxResult result = TridentMessageBox.ShowTridentConfirmationYesNoMessageBox(ManagementStudioResourceManager.GetString("ActivityDeleteConfirmationMsg"));
                if (result == MessageBoxResult.Yes)
                {
                    this.registryManagerService.DeleteActivity(this.EditableActivityViewModel);
                    this.editableActivityViewModel.IsDeleted = true;
                    if (this.DeleteCommand != null)
                    {
                        this.DeleteCommand.Execute(this.SelectedModel);
                        this.container.Resolve<IStatusMessageControl>().SetText(TridentUtilities.Properties.ManagementStudio.ActivityDeleteSuccess);
                    }
                }
            }
            catch (TridentCustomException ex)
            {
                string errorMsg = string.Empty;
                if (ex.CustomMessage != null)
                {
                    errorMsg = ex.CustomMessage.ToString();
                }
                else
                {
                    errorMsg = ex.Message;
                }

                TridentMessageBox.ShowTridentErrorMessageBox(errorMsg);
                this.RefreshCommand.Execute(this.SelectedModel);
            }
        }

        private void ImportActivityExecute()
        {
            SaveStateHandler saveStatus = this.EnforceSaveForChanges();
            if (saveStatus.SaveOptionSelected == SaveStatus.Valid)
            {
                ImportDialog importDialog = new ImportDialog(Application.Current.MainWindow, this.registryManagerService.RegistryConnection, ImportType.Activity);
                importDialog.ShowDialog();

                if (!importDialog.Canceled)
                {
                    bool importSucceeded = false;
                    StringBuilder messageBuilder = new StringBuilder();
                    try
                    {
                        importSucceeded = this.registryManagerService.ImportActivity(importDialog.FileReferences);
                    }
                    catch (TridentCustomException ex)
                    {
                        messageBuilder.Append(ex.Message);
                    }
                    catch (Exception ex)
                    {
                        messageBuilder.Append(ex.Message);
                    }

                    if (importSucceeded)
                    {
                        if (this.RefreshCommand != null)
                        {
                            this.RefreshCommand.Execute(this.SelectedModel);
                        }

                        this.container.Resolve<IStatusMessageControl>().SetText(ManagementStudioResourceManager.GetString("ActivityCatalogImportSuccessful"));
                    }
                    else if (!string.IsNullOrEmpty(messageBuilder.ToString()))
                    {
                        messageBuilder.Insert(0, SPACE);
                        messageBuilder.Insert(0, ManagementStudioResourceManager.GetString("ActivityCatalogImportFailed"));
                        TridentMessageBox.ShowTridentErrorMessageBox(messageBuilder.ToString());
                    }
                }
            }
        }

        private void BrowseCategoryExecute()
        {
            try
            {
                this.LaunchCategoryPicker();
                if (this.categoryPickerSelectedModel != null)
                {
                    this.EditableActivityViewModel.Parent = this.categoryPickerSelectedModel;
                }
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleUIException(ex);
                TridentMessageBox.ShowTridentErrorMessageBox("Error in launching category picker.");
            }
        }

        private void LaunchCategoryPicker()
        {
            this.categoryPickerSelectedModel = null;
            CategoryViewModel parentModel = null;
            if (this.SelectedModel != null)
            {
                ActivityViewModel currentModel = this.SelectedModel as ActivityViewModel;
                parentModel = currentModel.Parent as CategoryViewModel;
            }

            this.categoryPicker = new CategoryPicker(this.container, CategoryType.Activity, parentModel);
            this.categoryPicker.CategoryPickerOk += new EventHandler<CategoryEventArgs>(this.OnCategoryPickerOk);
            this.categoryPicker.CategoryPickerClose += new EventHandler(this.OnCategoryPickerClose);
            this.categoryPicker.Owner = Application.Current.MainWindow;
            this.categoryPicker.ShowDialog();
        }

        /// <summary>
        /// Event handler for Category Picker OK. 
        /// </summary>
        /// <param name="sender">CategoryPicker</param>
        /// <param name="e">CategoryEventArgs</param>
        private void OnCategoryPickerOk(object sender, CategoryEventArgs e)
        {
            if (e != null && this.EditableActivityViewModel != null)
            {
                CategoryViewModel selectedModel = e.CategoryPickerSelectedModel;
                if (selectedModel != null)
                {
                    string errorMsg = string.Empty;
                    bool validSelection = ActivityCenterPanePresenter.CategorySelectionValidator(this.EditableActivityViewModel, selectedModel, out errorMsg);
                    if (validSelection)
                    {
                        this.categoryPickerSelectedModel = selectedModel;
                        this.CloseCategoryPicker();
                    }
                    else if (!string.IsNullOrEmpty(errorMsg))
                    {
                        TridentMessageBox.ShowTridentErrorMessageBox(errorMsg);
                    }
                }
                else
                {
                    TridentMessageBox.ShowTridentErrorMessageBox(ManagementStudioResourceManager.GetString("SelectValidParent"));
                }
            }
        }

        /// <summary>
        /// Event handler for Category picker Close
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnCategoryPickerClose(object sender, EventArgs e)
        {
            this.CloseCategoryPicker();
        }

        /// <summary>
        /// Closes the Category Picker
        /// </summary>
        private void CloseCategoryPicker()
        {
            if (this.categoryPicker != null)
            {
                this.categoryPicker.Close();
                this.categoryPicker = null;
            }
        }

        /// <summary>
        /// Validates the Category Selection
        /// </summary>
        /// <param name="activityViewModel"></param>
        /// <param name="selectedParentModel"></param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        private static bool CategorySelectionValidator(ActivityViewModel activityViewModel, CategoryViewModel selectedParentModel, out string errorMsg)
        {
            bool valid = false;
            errorMsg = string.Empty;
            if (activityViewModel != null && selectedParentModel != null)
            {
                StringBuilder errorBuilder = new StringBuilder(ManagementStudioResourceManager.GetString("CategoryPickerInvalidSelection"));
                try
                {
                    valid = ActivityCenterPanePresenter.CheckRootAssignment(selectedParentModel);
                    if (valid)
                    {
                        valid = ActivityCenterPanePresenter.CheckActivityUniqueNameConstraint(activityViewModel, selectedParentModel);
                        if (!valid)
                        {
                            errorBuilder.Append(ManagementStudioResourceManager.GetString("ActivityNameNotValid"));
                        }
                    }
                    else
                    {
                        errorBuilder.Append(ManagementStudioResourceManager.GetString("ActivityCatalogInvalidRootSelection"));
                    }
                }
                catch (TridentCustomException ex)
                {
                    valid = false;
                    errorBuilder.Append(ex.Message);
                }

                if (!valid)
                {
                    errorMsg = errorBuilder.ToString();
                }
            }

            return valid;
        }

        private static bool CheckRootAssignment(CategoryViewModel selectedParentModel)
        {
            bool valid = false;
            if (selectedParentModel != null
                && selectedParentModel.RootCategory != null
                && !selectedParentModel.Id.Equals(selectedParentModel.RootCategory.Id))
            {
                valid = true;
            }

            return valid;
        }

        private static bool CheckActivityUniqueNameConstraint(ActivityViewModel activityViewModel, CategoryViewModel selectedParentModel)
        {
            bool valid = false;
            if (!string.IsNullOrEmpty(activityViewModel.Name) && !string.IsNullOrEmpty(activityViewModel.Name.Trim()))
            {
                valid = !selectedParentModel.Children.OfType<ActivityViewModel>().Any(model => model.Name.Equals(activityViewModel.Name.Trim(), StringComparison.OrdinalIgnoreCase)
                    && !model.Id.Equals(activityViewModel.Id));
            }

            return valid;
        }

        private SaveStateHandler EnforceSaveForChanges()
        {
            SaveStateHandler saveSuccess = new SaveStateHandler(SaveStatus.Valid);
            if (!this.saveInProgress
                && this.EditableActivityViewModel != null && !this.EditableActivityViewModel.IsDeleted
                && !this.EditableActivityViewModel.Equals(this.SelectedModel as ActivityViewModel))
            {
                MessageBoxResult userResult = TridentMessageBox.ShowTridentConfirmationYesNoMessageBox(ManagementStudioResourceManager.GetString("SaveChangesToActivity"));
                if (userResult == MessageBoxResult.Yes)
                {
                    this.SaveActivityChangesCommand.Execute(new object[] { false, saveSuccess });
                }
                else if (userResult == MessageBoxResult.No)
                {
                    this.Rollback();
                }
            }

            return saveSuccess;
        }

        #endregion Methods
    }
}
